#ifndef __TABLEZ_H
#define __TABLEZ_H

#include <stdarg.h>
#include <cstdio>
#include <iostream>
#include <stack>
#include <deque>
#include <cstring>
#include <algorithm>
#include <iomanip>

#include "bitArray.h"
#include "AATreeZ.h"

namespace dataStructureZ
{
	#if defined (_WINDOWS_) || defined (_WIN32)
	#	include <io.h> 
	#	define SEP '\\'
	#	define sSEP "\\"
	#	define mkDir(x) mkdir((x))
	#else	
	#	include <dirent.h>
	#	include <sys/stat.h>
	#	include <sys/types.h>
	#	define mkDir(x) mkdir((x), S_IRWXU)
	#	define SEP '/'
	#	define sSEP "/"
	#endif
	
	namespace DirTableZ
	{
		using std::stack;
		using std::deque;
		using std::ostream;
		using std::istream;
		
		class CodeInfo
		{
			public:ArrayBitController<uint8_t> code[256];
		};
		
		class FileSystemInfoBlock;
		class DirTable;
	
		class fileSystem_node
		{
			public:friend	class		DirTable;
			
		  //true----ele				false----tbl
			public:bool					type;
		  //public:bool					doNotDelete;
			public:char*				name;
		  //public:size_t				depth;
			
			public:union 
			{
				DirTable*				table;
				FileSystemInfoBlock*	element;
			}							data;
			
			public:fileSystem_node(void):
			type(true), /*doNotDelete(false), */name(nullptr)/*, depth(0)*/	{}	//don't forget to alloc space for name.
		
			public:fileSystem_node(const char* _name):
			type(true), /*doNotDelete(false), */name(new char[strlen(_name)+1])/*, depth(0)*/
			{
				strcpy(name, _name);
				data.element = nullptr;
			}
			
			public:fileSystem_node(const char* _name, FileSystemInfoBlock& blk):
			type(true), /*doNotDelete(false), */name(new char[strlen(_name)+1])/*, depth(0)*/
			{
				data.element = &blk;
				strcpy(name, _name);
			}
			
			public:fileSystem_node(const char* _name, DirTable* tbl):
			type(true), /*doNotDelete(false), */name(new char[strlen(_name)+1])/*, depth(0)*/
			{
				data.table = tbl;
				strcpy(name, _name);
			}
			
			public:fileSystem_node(const fileSystem_node& another):
			type(another.type), /*doNotDelete(false), */name(another.name), 
			/*depth(another.depth),*/ data(another.data)	{}
			
			public:fileSystem_node(FileSystemInfoBlock& blk):
			type(true), /*doNotDelete(false), */name(nullptr)/*, depth(0)*/
			{data.element = &blk;}
			
			public:fileSystem_node(DirTable* tbl):
			type(false), /*doNotDelete(false), */name(nullptr)/*, depth(0)*/
			{data.table = tbl;}
			/*
			public:fileSystem_node& operator = (const fileSystem_node& another)
			{
				type = another.type;
				doNotDelete = false;
				name = another.name;
				depth=another.depth;
				data = another.data;
			}
			*/
			
			public:bool operator == (const fileSystem_node& another) const
			{
				return type==another.type && strcmp(name, another.name)==0;
			}
			
			public:bool operator >= (const fileSystem_node& another) const
			{
				return (type!=another.type && type==true) || 
					   (type==another.type && strcmp(name, another.name)>=0);
					   
			}
			
			public:bool operator <= (const fileSystem_node& another) const
			{
				return (type!=another.type && type==false) ||
					   (type==another.type && strcmp(name, another.name)<=0);
			}
			
			public:bool operator > (const fileSystem_node& another) const
			{
				return (type!=another.type && type==true) || 
					   (type==another.type && strcmp(name, another.name)>0);
					   
			}
			
			public:bool operator < (const fileSystem_node& another) const
			{
				return (type!=another.type && type==false) ||
					   (type==another.type && strcmp(name, another.name)<0);
			}
			
			public:bool operator != (const fileSystem_node& another) const
			{
				return type!=another.type || strcmp(name, another.name)!=0;
			}
		};
		
		class FileSystemInfoBlock
		{
			public:friend class	DirTable;
			
			public:uint64_t		bitSize;
			public:uint64_t		sourceFileByteSize;
			public:uint8_t*		data;
			public:CodeInfo*	code;
			
			public:FileSystemInfoBlock(void):
			bitSize(0), sourceFileByteSize(0), 
			data(nullptr), code(nullptr){}
			
			public:FileSystemInfoBlock(CodeInfo* codeInfo, uint8_t* fileData = nullptr):
			bitSize(0), sourceFileByteSize(0), data(fileData), code(codeInfo){}
			
			public:FileSystemInfoBlock(const FileSystemInfoBlock& another):
			bitSize(another.bitSize),	sourceFileByteSize(another.sourceFileByteSize), 
			data(another.data),			code(another.code)	{}
			
			public:~FileSystemInfoBlock(void)
			{
				delete data;
				delete code;
			}
		};
		
		using AATree::AATreeZ;
		
		class DirTable
		{
			public:AATreeZ<fileSystem_node>	list;
			public:DirTable*				father;
		  //public:size_t					depth;
			
			public:static void delNode(const fileSystem_node& anode)
			{
				delete anode.name;
				if(anode.type)
					delete anode.data.element;
				else delete anode.data.table;
			}
			
			public:DirTable(DirTable* _father = nullptr):father(_father)/*, depth(0)*/
			{
				list.co_destructor = delNode;
			}
			
			public:DirTable(const DirTable& another):father(another.father)/*, depth(another.depth)*/
			{
				list.co_destructor = delNode;
				fileSystem_node temp;
				for
				(
					AATreeZ<fileSystem_node>::iterator i = 
						another.list.begin();
					i != another.list.end();
					++i
				)
				{
					temp.type = (*i).type;
					//temp.depth = (*i).depth;
					temp.name = new char[strlen((*i).name)+1];
					strcpy(temp.name, (*i).name);
					
					if((*i).type)
					{
						temp.data.element = new FileSystemInfoBlock(*(*i).data.element);											//!!!!!!!!!!!!!!!!!!!!!!!
						list.insert(temp);
					}
					else
					{
						temp.data.table = new DirTable(*(*i).data.table);
						list.insert(temp);
					}
				}
			}
			
			public:void clear(void)
			{
				list.makeEmpty();
			}
			
			public:~DirTable(void)
			{
				clear();
			}
			
			//Return nullptr while insert succeeded, or return the address of exist FileSystemInfoBlock.
			public:FileSystemInfoBlock* insert(const char* path, FileSystemInfoBlock* blk)
			{
				fileSystem_node temp;
				//path like "/a/b/c.d"
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
				
					if(readPtr[n] == '\0')	//new file
					{
						temp.type = true;
						//temp.depth = nowAt->depth;
						temp.name = new char[n+1];
						strncpy(temp.name, readPtr, n);
						temp.name[n] = '\0';
						temp.data.element = blk;
						
						//printf("-> %p\n", temp.data.element);
						
						fileSystem_node* ret = nullptr;
						
						if((ret=nowAt->list.insert(temp)) == nullptr)
							return nullptr;
						else return ret->data.element;	//insert fault, element existed
					}
					else	//find destinaition floader
					{
						temp.type = false;
						temp.name = new char[n+1];
						strncpy(temp.name, readPtr, n);
						temp.name[n] = '\0';
						
						fileSystem_node* nextFloader = nullptr;
						
						if((nextFloader=nowAt->list[temp]) != nullptr)
						{
							nowAt = nextFloader->data.table;
							readPtr += n+1;
							n = 0;
						}
						else	//create a new floader.
						{
							fileSystem_node tmp;
							tmp.type = false;
							//tmp.depth = nowAt->depth;
							tmp.name = new char[n+1];
							tmp.data.table = new DirTable;
							
							strncpy(tmp.name, readPtr, n);
							tmp.name[n] = '\0';
							
							tmp.data.table->father = nowAt;
							//tmp.data.table->depth  = nowAt->depth + 1;
							
							nowAt->list.insert(tmp);
							
							nowAt = tmp.data.table;
							readPtr += n+1;
							n = 0;
							//++nowAt->count;
						}
						
						delete temp.name;
					}
				}
				
				return nullptr;
			}
			
			public:static void createFloader(DirTable* at, const char* name)
			{
				fileSystem_node tmp;
				tmp.type = false;
				//tmp.depth = at->depth;
				tmp.name = strcpy(new char[strlen(name)+1], name);
				tmp.data.table = new DirTable;
				
				tmp.data.table->father = at;
				//tmp.data.table->depth  = at->depth + 1;
				
				at->list.insert(tmp);
			}
			
			public:void createFloader(const char* path)
			{
				fileSystem_node temp;
				//path like "/a/b/c.d"
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
					
					temp.type = false;
					temp.name = new char[n+1];
					strncpy(temp.name, readPtr, n);
					temp.name[n] = '\0';
										
					fileSystem_node* nextFloader = nullptr;
					
					if((nextFloader=nowAt->list[temp]) != nullptr)
					{
						nowAt = nextFloader->data.table;
						readPtr += n+1;
						n = 0;
					}
					else	//create a new floader.
					{
						fileSystem_node tmp;
						tmp.type = false;
						//tmp.depth = nowAt->depth;
						tmp.name = new char[n+1];
						tmp.data.table = new DirTable;
						
						strncpy(tmp.name, readPtr, n);
						tmp.name[n] = '\0';
						
						tmp.data.table->father = nowAt;
						//tmp.data.table->depth  = nowAt->depth + 1;
						
						nowAt->list.insert(tmp);
						nowAt = tmp.data.table;
						readPtr += n;
						n = 0;
						if(readPtr[n] == '/')
							++readPtr;
					}
					
					delete temp.name;
				}
			}
			
			public:bool remove(fileSystem_node* targetNode)
			{
				bool deled = false;
				list.del(*targetNode, delNode, &deled);
				return deled;
			}
			
			public:bool remove_withoutDelete(fileSystem_node* targetNode)
			{
				bool deled = false;
				list.del(*targetNode, nullptr, &deled);
				return deled;
			}
			
			public:void rename(fileSystem_node* targetNode, const char* newName)
			{
				if(targetNode != nullptr)
				{
					fileSystem_node tmp(*targetNode);
					list.del(*targetNode);
					delete tmp.name;
					tmp.name = strcpy(new char[strlen(newName)+1], newName);
					list.insert(tmp);
				}
			}
			
			public:bool remove
			(
				const char* path, 
				bool file_floader = true/*true--file  false--floader*/
			)
			{
				fileSystem_node temp;
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				//pathPtr.push(&nowAt->deleteBuffer);
				
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
				
					if(readPtr[n] == '\0')	//new file
					{
						temp.type = file_floader;
						temp.name = new char[n+1];
						strncpy(temp.name, readPtr, n);
						temp.name[n] = '\0';
						
						bool deled = false;
						 
						nowAt->list.del(temp, delNode, &deled);
						
						delete temp.name;
						return deled;
					}
					else	//find destinaition floader
					{
						temp.type = false;
						temp.name = new char[n+1];
						strncpy(temp.name, readPtr, n);
						temp.name[n] = '\0';
						
						fileSystem_node* nextFloader = nullptr;
						
						if((nextFloader=nowAt->list[temp]) != nullptr)
						{
							delete temp.name;
							nowAt = nextFloader->data.table;
							readPtr += n+1;
							n = 0;
						}
						else
						{
							delete temp.name;
							return false;
						}
					}
				}
			}
			
			public:DirTable* openDir(const char* path)
			{
				fileSystem_node temp;
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
					
					temp.type = false;
					temp.name = new char[n+1];
					strncpy(temp.name, readPtr, n);
					temp.name[n] = '\0';
					
					fileSystem_node* nextFloader = nullptr;
					
					if((nextFloader=nowAt->list[temp]) != nullptr)
					{
						nowAt = nextFloader->data.table;
						readPtr += n;
						n = 0;
						
						delete temp.name;
						if(readPtr[n] == '\0')
							return nowAt;
						else ++readPtr;
					}
					else
					{
						delete temp.name;
						return nullptr;
					}
				}
			}
			
			public:fileSystem_node* getNode(const char* path, DirTable** at = nullptr)
			{
				fileSystem_node temp;
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
					
					temp.type = false;
					temp.name = new char[n+1];
					strncpy(temp.name, readPtr, n);
					temp.name[n] = '\0';
					
					fileSystem_node* nextFloader = nullptr;
					
					if((nextFloader=nowAt->list[temp]) != nullptr)
					{
						nowAt = nextFloader->data.table;
						readPtr += n;
						n = 0;
						
						delete temp.name;
						if(readPtr[n] == '\0')
						{
							if(at != nullptr)
								*at = nowAt;
							return nextFloader;
						}	else ++readPtr;
					}
					else if(readPtr[n] == '\0')
					{
						temp.type = true;
						if((nextFloader=nowAt->list[temp]) != nullptr)
						{
							if(at != nullptr)
								*at = nowAt;
							return nextFloader;
						}
						else
						{
							delete temp.name;
							return nullptr;
						}
					}
					else
					{
						delete temp.name;
						return nullptr;
					}
				}
			}
			
			public:fileSystem_node* file_node(const char* path)
			{
				fileSystem_node temp;
				const char *readPtr = path+1;
				uint32_t n = 0;
				
				DirTable* nowAt = this;
				while(readPtr[n] != '\0')
				{
					while(readPtr[n] != '\0' && readPtr[n] != '/')
						++n;
					
					if(readPtr[n] == '\0')
						temp.type = true;
					else temp.type = false;
					
					temp.name = new char[n+1];
					strncpy(temp.name, readPtr, n);
					temp.name[n] = '\0';
					
					fileSystem_node* nextFloader = nullptr;
					
					if((nextFloader=nowAt->list[temp]) != nullptr)
					{
						delete temp.name;
						if(readPtr[n] != '\0')
						{
							nowAt = nextFloader->data.table;
							readPtr += n;
							n = 0;
							++readPtr;
						}
						else return nextFloader;
					}
					else
					{
						delete temp.name;
						return nullptr;
					}
				}
			}
			
			public:FileSystemInfoBlock* file(const char* path)
			{
				return file_node(path)->data.element;
			}
			
			public:DirTable* getBack(void)
			{
				return father;
			}
			/*
			public:static void outputView(ostream& ost, const fileSystem_node& nde)
			{
				for(size_t i=0 ; i<nde.depth ; ++i)
					if(i+1 != nde.depth)
						ost<<"|   ";
					else ost<<"|---";
					
				if(nde.type)
				{
					ost<<"<FIL> ";
					ost<<std::left<<std::setw(64 - nde.depth * 4)<<nde.name<<std::right<<std::setw(24)<<nde.data.element->sourceFileByteSize<<" Bytes"<<std::endl;
				}
				else
				{
					ost<<"<DIR> ";
					ost<<std::left<<std::setw(32)<<nde.name<<std::endl;
					nde.data.table->list.midOrder(ost, outputView);
				}
			}
			*/
			public:static void outputView2(ostream& ost, const fileSystem_node& nde)
			{
				if(nde.type)
				{
					ost<<"<FIL> ";
					ost<<std::left<<std::setw(32)<<nde.name<<std::right<<std::setw(16)<<nde.data.element->sourceFileByteSize<<" Bytes"<<std::endl;
				}
				else
				{
					ost<<"<DIR> ";
					ost<<std::left<<std::setw(32)<<nde.name<<std::endl;
				}
			}
			/*
			public:friend ostream& operator << (ostream& ost, const DirTable& tbl)
			{
				tbl.list.midOrder(ost, outputView);
				
				ost<<"==============================================< END >==============================================="<<std::endl;
				return ost;
			}
			*/
			public:void viewAll_current(void)
			{
				std::string perfix = "";
				_viewAll_current(perfix, 0);
			}
			
			private:void _viewAll_current(std::string& pfix, size_t depth = 0)
			{
				short spaceWidth = 0;
				#if defined(__ANDROID__)
					spaceWidth = 31 - depth * 2;
				#else
					spaceWidth = 64 - depth * 4;
				#endif
				for
				(
					AATreeZ<fileSystem_node>::mid_iterator i = list.mid_begin();
					i != list.mid_end();
					++i
				)
				{
					std::cout<<pfix;
					#if defined(__ANDROID__)
						std::cout<<"|-";
					#else
						std::cout<<"|---";
					#endif
					if(i->type)
					{
						
						std::cout<<"<FIL> ";
						#if defined(__ANDROID__)
							std::cout<<std::left<<std::setw(spaceWidth)<<i->name<<std::right<<std::setw(22)<<i->data.element->sourceFileByteSize<<" Bytes"<<std::endl;
						#else
							std::cout<<std::left<<std::setw(spaceWidth)<<i->name<<std::right<<std::setw(20)<<i->data.element->sourceFileByteSize<<" Bytes"<<std::endl;
						#endif
					}
					else
					{
						std::cout<<"<DIR> ";
						#if defined(__ANDROID__)
							pfix += (i.hasNext()? "| ": "  ");
							std::cout<<std::left<<std::setw(51)<<i->name<<std::endl;
						#else
							pfix += (i.hasNext()? "|   ": "    ");
							std::cout<<std::left<<std::setw(82)<<i->name<<std::endl;
						#endif
						i->data.table->_viewAll_current(pfix, depth + 1);
						#if defined(__ANDROID__)
							pfix = pfix.substr(0, pfix.length() - 2);
						#else
							pfix = pfix.substr(0, pfix.length() - 4);
						#endif
						
					}
				}
				std::cout<<pfix<<std::endl;
			}
			
			
			public:ostream& showCurrentDir(ostream& ost)
			{
				list.midOrder(ost, outputView2);
				return ost;
			}
			
			public:void getView(std::deque<fileSystem_node*>& userList)
			{
				userList.clear();
				for
				(
					AATreeZ<fileSystem_node>::mid_iterator i=list.mid_begin();
					i != list.mid_end();
					++i
				)	userList.push_back(&(*i));
			}
		};
	};
};
#endif
